package com.univocity.trader;

import com.univocity.trader.account.*;
import com.univocity.trader.candles.*;
import com.univocity.trader.config.*;
import com.univocity.trader.indicators.*;
import com.univocity.trader.indicators.base.*;
import com.univocity.trader.strategy.*;
import com.univocity.trader.utils.*;
import org.slf4j.*;

import java.net.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * 一个 {@code Exchange} 实现提供了关于在实时的交易经纪人或交易所中可用的工具及其价格的信息。
 *
 * @param <T> ｛@code Exchange｝特定的蜡烛/tick 类型， 将转换为用于内存计算的｛@link candle｝
 *           和用于存储的带有｛@link#generatePreciseCanceCandle（Object）｝的｛@link PreciseCcandle｝。
 *
 * @see Candle
 * @see PreciseCandle
 * @see SymbolInformation
 * @see ClientAccount
 * @see com.univocity.trader.simulation.SimulatedExchange
 * @see com.univocity.trader.simulation.SimulatedClientAccount
 */
public interface Exchange<T, C extends AccountConfiguration<C>> {
	Logger log = LoggerFactory.getLogger(Exchange.class);

	/**
	 * 为给定符号在给定时间间隔内提供最新的交易所特定的蜡烛/滴答。此方法旨在快速返回，因此返回的列表不应包含超过最后 30 分钟的价格信息。
	 *
	 * @param symbol 应该从交易所返回最新价格信息的符号（例如 BTCUSDT、MSFT 等）。
	 * @param interval 每个蜡烛的持续时间。例如，如果为 {@code TimeInterval.minutes(5)}，则应理想地返回最新的 5 分钟蜡烛（通常包含开盘和收盘时间、开盘和收盘价格、最高和最低价格以及交易量）。
	 * 并非需要提供所有的信息，但至少应该提供收盘价格和收盘时间。
	 *
	 * @return 最近的交易所特定的蜡烛/滴答，这些蜡烛/滴答将被转换为内存计算中的 {@link Candle} 和 {@link PreciseCandle} 以供存储。
	 */
	IncomingCandles<T> getLatestTicks(String symbol, TimeInterval interval);

	/**
	 * 返回给定符号在给定时间间隔内的历史蜡烛/滴答，用于给定时间段。用于填充由 {@link CandleRepository} 管理的本地数据库，用于回测和策略开发。
	 * 如果实时交易所一次返回的蜡烛数量有限制，那么应该尊重这个限制，并且可以通过内部缩短由 {@code startTime} 或 {@code endTime} 给出的间隔。
	 *
	 * 方法 {@link DatabaseCandleRepository#fillHistoryGaps(Exchange, String, Instant, TimeInterval) }
	 * 将循环遍历历史中的间隙，并根据需要从交易所请求更多蜡烛以填充它们。
	 *
	 * @param symbol 应该从交易所返回最新价格信息的符号（例如 BTCUSDT、MSFT 等）。
	 * @param interval 每个蜡烛的持续时间。例如，如果为 {@code TimeInterval.minutes(5)}，则应理想地返回最新的 5 分钟蜡烛（通常包含开盘和收盘时间、开盘和收盘价格、最高和最低价格以及交易量）。
	 * 并非需要提供所有的信息，但至少应该提供收盘价格和收盘时间。
	 * @param startTime 需要返回的历史数据开始的时间（以毫秒为单位）。
	 * @param endTime 需要返回的历史数据结束的时间（以毫秒为单位）。
	 *
	 * @return 给定时间段（或更短）的历史交易所特定的蜡烛/滴答，这些蜡烛/滴答将被转换为内存计算中的 {@link Candle} 和 {@link PreciseCandle} 以供存储。
	 */
	IncomingCandles<T> getHistoricalTicks(String symbol, TimeInterval interval, long startTime, long endTime);

	/**
	 * 将一个 {@code Exchange}-specific 的蜡烛/滴答转换为框架用于数据库存储的 {@link PreciseCandle}，不会造成精度损失。
	 *
	 * 在返回的 {@link PreciseCandle} 中至少应该填充收盘价格和开盘/收盘时间。如果输入的蜡烛没有相应的数据且无法推导出，则应将 {@link PreciseCandle} 的字段设置为 {@code BigDecimal.ZERO}。
	 *
	 * @param exchangeCandle 需要转换为 {@link PreciseCandle} 的 {@code Exchange}-specific 蜡烛/滴答详情。
	 *
	 * @return 一个填充了来自给定交易所特定蜡烛的所有可用详细信息的 {@link PreciseCandle}。
	 */
	PreciseCandle generatePreciseCandle(T exchangeCandle);

	/**
	 * 启动一个线程，该线程定期向底层连接发送 keep-alive 消息。
	 */
    default void startKeepAlive() { }

	/**
	 * 连接到实时交易所流，以接收实时信号，这些信号将委托给给定的 {@link TickConsumer}。
	 *
	 * 在实时流之上，{@link LiveTrader} 将不断检查订阅符号的信号更新。如果 {@link LiveTrader} 在给定的 {@code tickInterval} 内没有收到价格更新，将使用 {@link #getLatestTick(String, TimeInterval)} 轮询符号价格。
	 *
	 * @param symbols 要订阅的符号的逗号分隔列表。
	 * @param tickInterval 要接收的信号的频率，如每 1 分钟、1 小时、5 秒等（由交易所支持）。
	 * @param consumer {@code Exchange}-specific 蜡烛/滴答详情的消费者，其数据需要转换为 {@link Candle}，然后提交进行进一步处理（即 {@link Strategy} 分析、{@link Signal} 生成和可能的交易）。
	 */
	void openLiveStream(String symbols, TimeInterval tickInterval, TickConsumer<T> consumer);

	/**
	 * 断开与使用 {@link #openLiveStream(String, TimeInterval, TickConsumer)} 打开的实时交易所流的连接。
	 *
	 * @throws Exception 在关闭流时发生任何错误时抛出。
	 */
	void closeLiveStream() throws Exception;

	/**
	 * 所有可用符号的快速价格更新。用于计算当前账户头寸、可用于交易的资金和（在{@linkAccountManager#allocateFunds（String）}中），
	 * 并发送带有账户当前净值的电子邮件通知。
	 * @return 交易所交易的所有符号及其对应价格的映射。
	 */
	Map<String, double[]> getLatestPrices();

	/**
	 * 返回每个交易符号的详细信息，例如最小订单大小和用于正确格式化和正确生成订单的小数位数（一些交易所可能不接受如 {@code 14.44321} 这样的价格，而需要 {@code 14.4432} 代替）。
	 *
	 * 如果不适用，只需返回 {@code null}。
	 *
	 * @return 与此交易所中所有可用符号相关联的 {@link SymbolInformation} 的映射，如果适用。
	 */
	Map<String, SymbolInformation> getSymbolInformation();

	/**
	 * 返回给定符号或交易对的最新价格。
	 *
	 * @param assetSymbol 资产的符号（例如 BTC、EUR、MSFT 等）。
	 * @param fundSymbol 用于表示给定资产价格的资金符号（ETH、USD、BRL）。框架总是发送一个符号，如果没有交易对存在，将发送 {@link AccountConfiguration#referenceCurrency()} 提供的 {@code referenceCurrency}。
	 * 如果不适用（例如在只交易股票的情况下，账户只用 USD 购买股票），则可以忽略。
	 *
	 * @return 给定 {@code assetSymbol} 或对的最新价格。
	 */
	double getLatestPrice(String assetSymbol, String fundSymbol);

	/**
	 * 使用给定的 {@code apiKey} 和 {@code secret} 打开一个客户端账户，这授权了 {@link Client} 进行交易和更新其账户余额。
	 *
	 * @param accountConfiguration 要用于连接到所需账户的客户端账户配置（通常包含 API 密钥和秘密）。
	 *
	 * @return 交易所特定实现的 {@link ClientAccount} 接口，允许 {@link Client} 通过此框架进行交易。
	 */
	ClientAccount connectToAccount(C accountConfiguration);

	/**
	 * 为给定符号在给定时间间隔内提供最新的交易所特定的蜡烛/滴答。
	 *
	 * 用于在实时流没有更新时轮询蜡烛详细信息。{@link LiveTrader} 将持续检查通过 {@link #openLiveStream(String, TimeInterval, TickConsumer)} 订阅的符号的信号更新。
	 *
	 * @param symbol 应该从交易所返回最新价格信息的符号（例如 BTCUSDT、MSFT 等）。
	 * @param interval 蜡烛的持续时间。例如，如果为 {@code TimeInterval.minutes(5)}，则应理想地返回最新的 5 分钟蜡烛（通常包含开盘和收盘时间、开盘和收盘价格、最高和最低价格以及交易量）。
	 * 并非需要提供所有的信息，但至少应该提供收盘价格和收盘时间。
	 *
	 * @return 交易所特定的蜡烛/滴答类型，它将被转换为内存计算中的 {@link Candle} 和 {@link PreciseCandle} 以供存储。
	 */
	default T getLatestTick(String symbol, TimeInterval interval) {
		return null;
	}

	/**
	 * 处理从交易所服务器产生的错误，这些错误可能是在实时流不可用、缓慢或不稳定时轮询最新价格时产生的。
	 *
	 * {@link LiveTrader} 将持续检查通过 {@link #openLiveStream(String, TimeInterval, TickConsumer)} 订阅的符号的信号更新。如果给定 {@link TimeInterval} 内没有收到更新，将通过 {@link #getLatestTick(String, TimeInterval)} 轮询交易所，以确保使用的 {@link Strategy} 生成的信号是可靠的。
	 *
	 * @param symbol 在实时流中没有收到最新蜡烛/滴答信息，而是轮询的符号。
	 * @param e 在轮询给定符号的最新滴答时发生的错误。
	 *
	 * @return 在重试执行轮询操作之前要等待的时间。
	 */
	default TimeInterval handlePollingException(String symbol, Exception e) {
		String message = "execute polling for " + symbol;
		if (e.getCause() instanceof TimeoutException) {
			log.error("Timeout trying to " + message, e);
		} else if (e.getCause() instanceof UnknownHostException) {
			log.error("Unable to " + message + ". Server is offline.", e);
			return TimeInterval.minutes(1);
		} else {
			log.error("Error trying to " + message, e);
		}
		return null;
	}
	/**
	 * 返回交易所通过 {@link #getHistoricalTicks(String, TimeInterval, long, long)} 获取历史数据时返回的最大蜡烛数量。
	 *
	 * @return 从交易所获取历史数据时一次请求返回的最大蜡烛数量，如果没有限制，则返回 {@code 0}。
	 */
	default int historicalCandleCountLimit() {
		return 0;
	}

	/**
	 * 返回在执行从交易所获取数据的请求之前必须等待的最短时间间隔。
	 * 此间隔用于 {@link #waitBeforeNextRequest(long)}。
	 *
	 * @return 每次请求要等待的时间。
	 */
	default long timeToWaitPerRequest() {
		return 1_000;
	}

	/**
	 * 方便的方法，用于控制向交易所发送请求的速率。当前线程将睡眠 {@link #timeToWaitPerRequest()} 指定的时间间隔。
	 *
	 * 目前用于在执行历史数据回填时，通过 {@link CandleHistoryBackfill#fillHistoryGaps(Exchange, String, Instant, Instant, TimeInterval)} 请求历史数据增量。
	 *
	 * @param lastRequestTime 上一次请求的时间戳，用于扣除在处理上一请求返回的响应时已经花费的时间。
	 */
	default void waitBeforeNextRequest(long lastRequestTime) {
		long sleepTime = timeToWaitPerRequest() - (System.currentTimeMillis() - lastRequestTime);
		sleepTime = Math.min(sleepTime, timeToWaitPerRequest());
		if (sleepTime > 0) {
			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {
				log.error("Thread interrupted waiting for next request to exchange " + getClass().getName(), e);
				Thread.currentThread().interrupt();
			}
		}
	}

//	boolean isDirectSwitchSupported(String currentAssetSymbol, String targetAssetSymbol);

	default String getName(){
		return getClass().getSimpleName();
	}
}